package at.ac.ait.archistar.engine.serverinterface;
import at.ac.ait.archistar.backendserver.OzymandiasServer;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import javax.net.ssl.SSLEngine;
import at.ac.ait.archistar.trustmanager.SSLContextFactory;
import at.archistar.bft.client.ClientResult;
import at.archistar.bft.client.ResultManager;
import at.archistar.bft.exceptions.InconsistentResultsException;
import at.archistar.bft.messages.ClientCommand;
import at.archistar.bft.messages.TransactionResult;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.ssl.SslHandler;
/**
* this is the main interface for Clients contacting replicas
*
* @author andy
*/
public class OzymandiasClient {
private final Map<Integer, Integer> serverList;
private final Map<Integer, Channel> channelList;
private int f = 1;
private final EventLoopGroup group;
private final ResultManager resultManager;
public OzymandiasClient(Map<Integer, Integer> serverList, int f, NioEventLoopGroup group) {
this.serverList = serverList;
this.channelList = new HashMap<>();
this.f = f;
this.group = group;
this.resultManager = new ResultManager();
}
/**
* Sends a message and waits for all replicas replies
*
* @param msg the message to be sent
* @return
*/
public ClientResult sendRoundtripMessage(Map<Integer, ClientCommand> msg) {
/* TODO: check if all clientIds and clientSequences are the same */
int clientId = msg.get(0).getClientId();
int clientSequence = msg.get(0).getClientSequence();
/* create a new operation wait object */
ClientResult result = this.resultManager.addClientOperation(f, clientId, clientSequence);
/* asynchronously send a message to all replicas */
for (Entry<Integer, ClientCommand> e : msg.entrySet()) {
channelList.get(e.getKey()).writeAndFlush(e.getValue());
}
/* wait for answers */
result.waitForEnoughAnswers();
return result;
}
public void connect() throws Exception {
for (Entry<Integer, Integer> e : this.serverList.entrySet()) {
int serverId = e.getKey();
int serverPort = e.getValue();
this.channelList.put(serverId, connectServer(serverPort));
}
}
@SuppressFBWarnings("SIC_INNER_SHOULD_BE_STATIC_ANON")
private Channel connectServer(int port) throws Exception {
final OzymandiasClientHandler handler = new OzymandiasClientHandler(this);
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
SSLEngine engine = SSLContextFactory.getClientContext().createSSLEngine();
engine.setUseClientMode(true);
ch.pipeline().addLast(
new SslHandler(engine),
new ObjectEncoder(),
new ObjectDecoder(OzymandiasServer.maxObjectSize, ClassResolvers.cacheDisabled(null)),
handler);
}
});
return b.connect("127.0.0.1", port).sync().channel();
}
/**
* adds a replica's result. This is used to determine when enough results
* for determining an operations result were received
*
* @param clientId the result's client id
* @param clientSequence the result's client sequence
* @param tx the result
* @throws InconsistentResultsException seems like a faulty replica did send
* something unexpected
*/
public void addReplicaResult(int clientId, int clientSequence, TransactionResult tx) throws InconsistentResultsException {
resultManager.addClientResponse(clientId, clientSequence, tx);
}
}